﻿using System;
using System.Collections.Generic;
using UnityEngine;

namespace PSCrewManager
{
    public class ModuleCrewManager : PartModule
    {
        private ProtoCrewMember _pickedCrewMember; //Store kerbal for transfer
        private Part _pickedCrewMemberPart; //Part in which kerbal sits

        [KSPField]
        public bool AllowUncrewableModuleTransfer; //Allow kerbals transfer through parts with CrewCapacity = 0

        [KSPEvent(guiActive = true, guiName = "Move Kerbals")]
        public void GetKerbal()
        {
            WriteLog("Button clicked. Prepare to show GUI");
            ShowGUI(DrawPickGUI);
        }

        /// <summary>
        /// Draw Kerbal select GUI and store selected
        /// </summary>
        private void DrawPickGUI()
        {
            GUI.skin = HighLogic.Skin;
            foreach (var crewMemberPart in vessel.Parts)
            {
                //Get all crewable parts
                if (crewMemberPart.protoModuleCrew.Count <= 0) continue;
                var partScreenPoint = GetScreenPos(crewMemberPart);
                WriteLog("Check part '" + crewMemberPart.ConstructID);
                for (int i = 0; i < crewMemberPart.protoModuleCrew.Count; i++)
                {
                    var crewMember = crewMemberPart.protoModuleCrew[i];
                    WriteLog("Set button at " + partScreenPoint);
                    var rect =  GetButtonRect(partScreenPoint, crewMember.name);
                    rect.y += rect.height*i;
                    if (GUI.Button(rect, crewMember.name))
                    {
                        WriteLog("Getting " + crewMember.name);
                        //store kerbal and part
                        _pickedCrewMember = crewMember;
                        _pickedCrewMemberPart = crewMemberPart;

                        HideGUI(DrawPickGUI);
                        ShowGUI(DrawPutGUI);
                    }
                }
            }

            //Show CancelButton at CrewManager position
            var thisPos = GetScreenPos(this.part);
            WriteLog("Set Cancel button at " + thisPos);
            if (GUI.Button(GetButtonRect(thisPos, "Cancel"), "Cancel"))
            {
                WriteLog("Crew managment canceled.");
                _pickedCrewMember = null;
                _pickedCrewMemberPart = null;
                HideGUI(DrawPickGUI);
            }
        }

        //Draw destination part gui
        private void DrawPutGUI()
        {
            GUI.skin = HighLogic.Skin;
            //var oldPart = vessel.Parts.Find(p => p.protoModuleCrew.Contains(_pickedCrewMember));
            if (_pickedCrewMemberPart != null && _pickedCrewMember != null)
            {
                //find all avalible parts to transfer
                var dests = AllowUncrewableModuleTransfer ? vessel.Parts : GetAvaliableDestinations(_pickedCrewMemberPart);
                foreach (var crewablePart in dests)
                {
                    //filter parts with enough space
                    if (crewablePart.CrewCapacity <= 0 ||
                        crewablePart.protoModuleCrew.Count >= crewablePart.CrewCapacity ||
                        _pickedCrewMemberPart == crewablePart)
                        continue;

                    var partScreenPoint = GetScreenPos(crewablePart);
                    WriteLog("Set button for " + crewablePart.ConstructID + " at " + partScreenPoint);
                    if (GUI.Button(GetButtonRect(partScreenPoint, crewablePart.partInfo.title), crewablePart.partInfo.title))
                    {
                        WriteLog("Putting " + _pickedCrewMember.name + " to " + crewablePart.name);
                        HideGUI(DrawPutGUI);

                        MoveKerbal(_pickedCrewMember, _pickedCrewMemberPart, crewablePart, vessel);

                        //clear for safe
                        _pickedCrewMember = null;
                        _pickedCrewMemberPart = null;
                    }

                }

                //Show CancelButton at CrewManager position
                var thisPos = GetScreenPos(this.part);
                WriteLog("Set Cancel button at " + thisPos);
                if (GUI.Button(GetButtonRect(thisPos,"Cancel"), "Cancel"))
                {
                    WriteLog("Crew managment canceled.");
                    _pickedCrewMember = null;
                    _pickedCrewMemberPart = null;
                    HideGUI(DrawPickGUI);
                }
            }
            else
            {
                WriteLog("Error: _pickedCrewMemberPart != null && _pickedCrewMember != null");
            }
        }

        public static Rect GetButtonRect(Vector2 position, string text)
        {
            var style = GUI.skin.GetStyle("Button");
            var buttonSize = style.CalcScreenSize(style.CalcSize(new GUIContent(text)));
            return new Rect(position.x, position.y, buttonSize.x, buttonSize.y);
        }

        /// <summary>
        /// Get screen postition of part
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public static Vector2 GetScreenPos(Component p)
        {
            var sp = Camera.main.WorldToScreenPoint(p.transform.localPosition);//mainCamera also works
            return new Vector2(sp.x, Camera.main.pixelHeight - sp.y);
        }

        private static void ShowGUI(Callback drawGui)
        {
            RenderingManager.AddToPostDrawQueue(3, drawGui);//start the GUI
        }

        private static void HideGUI(Callback drawGui)
        {
            RenderingManager.RemoveFromPostDrawQueue(3, drawGui);
        }

        /// <summary>
        /// Write log with mark... a bit cleaner
        /// </summary>
        /// <param name="s"></param>
        public static void WriteLog(string s)
        {
#if DEBUG
            Debug.Log("PS_CM: " + s);
#endif
        }

        /// <summary>
        /// Get all parts with CrewCapacity>0 conected to source
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static List<Part> GetAvaliableDestinations(Part source)
        {
            var list = new List<Part>();

            var parent = GetParent(source, p => p.CrewCapacity > 0);

            GetChilds(parent, p => p.CrewCapacity > 0, list);

            return list;
        }

        /// <summary>
        /// Get all child parts of source part by prdicate
        /// </summary>
        /// <param name="source"></param>
        /// <param name="p"></param>
        /// <param name="list"></param>
        public static void GetChilds(Part source, Predicate<Part> p, List<Part> list)
        {
            if (!p.Invoke(source)) return;
            list.Add(source);
            foreach (var child in source.children)
                GetChilds(child, p, list);
        }

        /// <summary>
        /// Get hiest parent of source part by predicate
        /// </summary>
        /// <param name="source"></param>
        /// <param name="p"></param>
        /// <returns></returns>
        public static Part GetParent(Part source, Predicate<Part> p)
        {
            if (p.Invoke(source) && source.parent != null)
                return GetParent(source.parent, p);
            else return source;
        }

        /// <summary>
        /// Here be dragons. Transfer kerbal from source part to destination. Unsafe.
        /// </summary>
        /// <param name="crewMember"></param>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        /// <param name="currentVessel"></param>
        public static void MoveKerbal(ProtoCrewMember crewMember, Part source, Part destination, Vessel currentVessel)
        {
            crewMember.KerbalRef = null;
            source.RemoveCrewmember(crewMember);
            WriteLog(crewMember.name + " removed from " + source.name);
            destination.AddCrewmember(crewMember);
            WriteLog(crewMember.name + " added to " + destination.name);
            crewMember.StartRespawnPeriod();//do not know if this is needed
            currentVessel.SpawnCrew();//or this... but works
            WriteLog(crewMember.KerbalRef.InPart.name);
        }
    }
}
